/** Archivo: nodo.java
 *  Autores: Grace Gimon 08-10437
 *           Daniel Montenegro 08-10762
 * Define un servidor que espera comandos provenientes de un cliente, y en funcion de los
 * comando que recibe, le envia una respuesta al cliente.
 */

import java.util.logging.Level;
import java.util.logging.Logger;
import nanoxml.*;
import java.util.*;
import java.io.*;
import java.net.*;


public class nodo extends Thread {
    private ServerSocket server;
    private BufferedReader serverIn;
    private PrintWriter serverOut;
    private int portNumb = 10762;
    private String nombre;
    private List<String> conocidos;
    private String biblioteca;
    private List<Cancion> playlistActual;
    private int numCancion = 0;
    private Socket clientSocket;
    private String miIp;

public nodo(int puerto, String biblioteca, String nombre,Socket clientSocket){

    this.portNumb = puerto;
    this.biblioteca = biblioteca;
    this.nombre = nombre;
    this.playlistActual = new ArrayList<Cancion>();
    this.clientSocket = clientSocket;

    // Canal de entrada de datso provenientes del cliente
     this.serverIn = null;

    // Canal de salida de datos hacia el cliente
     this.serverOut = null;

    System.out.println("Servidor de Prueba en Java, activado!" );
  }

/* public void run()
 *  Procedimiento que realiza el nodo al aceptar una solicitud de conexion.
 *  Se encarga de procesar las solitudes del cliente con socket clientSocket
 */
public void run(){

    try{

    // espera a que se conecte alg?n cliente a nuestro puerto
     System.out.println("Estado antes de aceptar:"+this.getServer());

 // Creacion de los canales de entrada y salida paracomunicarse con el cliente
     this.serverIn =  new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
     this.serverOut = new PrintWriter(clientSocket.getOutputStream(), true);

// A partir de este punto un cliente ya se ha conectado al servidor
//      System.out.println("Clientsocket despues de aceptar: " + clientSocket);


    // env?a la l?nea de identificacion inicial del servidor

       boolean quit = false;
        // ?Qu? tiene que decirnos el cliente?
       while( ! quit ) {
        this.serverOut.println("Servidor de Prueba en Java 1.2, " + new Date() );
        String clientRequest = this.serverIn.readLine();

        if (clientRequest.startsWith("c") || clientRequest.startsWith("C")){
             String[] linea = clientRequest.split("\t");
             this.realizarConsultaCancion(linea);
             System.out.println("El cliente me dice: "+clientRequest);
         }
        else if (clientRequest.startsWith("a") || clientRequest.startsWith("A")){
              String[] linea = clientRequest.split("\t");
              this.miIp = linea[1].trim();
              List<String> hostsConsiderables = this.conocidos; //SON TODOS LOS MIOS POR DEFAULT
              if (linea[2].compareTo("N")==0){
                  //CONECTARME A TODOS MIS ALCANZABLES
              }
              else{
                  String[] param = linea[2].split(" ");
                  //NO DEBO CONSIDERAR LOS HOSTS DE MI PREDECESOR
                  List<String> predecesores = new ArrayList<String>();
                  for(int i= 2; i<param.length; i++){
                      predecesores.add(param[i]);
                  }
                  hostsConsiderables = obtenerHostsAlcanzables(predecesores);
              }
               System.out.println("Voy a verificar mis alcanzables \n");
               List<String> nodosAlc = this.conectarmeConAlcanzables(hostsConsiderables);
               this.serverOut.println(listaToString(nodosAlc));

        }
        else if (clientRequest.equalsIgnoreCase("q")){
            quit = true;
        }
        System.out.println("Fin loopp \n");
        }
        System.out.println("Server says: Bye \n");
        }
         catch (IOException e) {
         System.out.println("Excepcion E/S en server.in.readLine() " + e);
         }



}

/* public static void main(String[] args)
 * Param: args: Numero de puerto
 *              Path del archivo de los nodos conocidos
 *              Path del archivo de la biblioteca XSPF
 *              Nombre del nodo
 * Se encarga de la creacion de los hilos del servidor para atender
 * multiples solicitudes.
 */
public static void main(String[] args) {

    if (args.length < 8){
     System.err.print("Error: Numero invalido de parametros");
    }

    int puerto = Integer.parseInt(args[1]);
    String archConocidos = args[3];
    String biblioteca = args[5];
    String nombreNodo = args[7];
    try {
    ServerSocket server = new ServerSocket(puerto);
    while(true){
                nodo nodoServer = new nodo(puerto,biblioteca,nombreNodo,server.accept());
                nodoServer.setConocidos(nodoServer.extraerConocidos(archConocidos));
                nodoServer.start();
    }
    } catch (IOException ex) {
                Logger.getLogger(nodo.class.getName()).log(Level.SEVERE, null, ex);
    }

}

/* public List<Cancion> buscarCancionPorTitulo(String titulo)
 * Param: titulo: Nombre del titulo de una cancion
 * Return: Lista de las canciones que poseen el titulo "titulo"
 */
public List<Cancion> buscarCancionPorTitulo(String titulo){
   List<Cancion> cancionesTitulo = new ArrayList<Cancion>();
    for (int i= 0; i<this.playlistActual.size(); i++ ){
        if (this.playlistActual.get(i).getTitle().compareToIgnoreCase(titulo) == 0){
            cancionesTitulo.add(this.playlistActual.get(i));
        }
   }

    return cancionesTitulo;
}
/* public List<Cancion> buscarCancionPorAutor(String autor)
 * Param: autor: Nombre del autor/creador de una cancion
 * Return: Lista de las canciones del autor/creador "autor"
 */
public List<Cancion> buscarCancionPorAutor(String autor){
   List<Cancion> cancionesAutor = new ArrayList<Cancion>();
    for (int i= 0; i<this.playlistActual.size(); i++ ){
        if (this.playlistActual.get(i).getCreator().compareToIgnoreCase(autor) == 0){
            cancionesAutor.add(this.playlistActual.get(i));
        }
   }

    return cancionesAutor;
}
/*public static String toStringListaCanciones(List<Cancion> canciones)
 * Param: canciones: Lista de canciones a imprimir
 * Return: String de todas las canciones separadas por \n
 */
public static String toStringListaCanciones (List<Cancion> canciones){
    String s = "";
    for(int i= 0; i<canciones.size(); i++){
         String c = canciones.get(i).toString();
          s = s + c;
         }
    return s;

}
/* public List<String> extraerConocidos(String archconocidos)
 * Param: archConocidos: Nombre del archivo de los nodos conocidos.
 * Esta funcion lee del archivo en el cual nos otorgan todos los IP/hostname
 * de los nodos conocidos.
 * Return: una lista con los inet Address.
 */
public List<String> extraerConocidos(String archConocidos){
   BufferedReader bufin;
   List<String> listaConocidos = new ArrayList<String>();
        try{

          bufin = new BufferedReader(new FileReader(archConocidos));
           try{
            String linea = bufin.readLine();
            while (linea != null){
               InetAddress inet = InetAddress.getByName(linea);
               listaConocidos.add(inet.toString());
               linea = bufin.readLine();
            }
           } catch (Exception ioe){
                 System.err.println("Error Abriendo el archivo");
             }
        } catch (FileNotFoundException fnfe){
          System.err.println("No se encontro el archivo");
        }

return listaConocidos;
}
/* public void obtenerBiblioteca(List<Cancion> playlist, String biblioteca)
 * Param: playlist : Lista de canciones a retornar
 *        biblioteca : Path del archivo de la biblioteca XSPF
 */
public void obtenerBiblioteca(List<Cancion> playlist, String biblioteca)
      throws Exception {
            FileReader reader = new FileReader(biblioteca);
            XMLElement xml = new XMLElement();
            xml.parseFromReader(reader);

                Enumeration enumChild = xml.enumerateChildren();
            XMLElement xml1 = (XMLElement)enumChild.nextElement(); //Tracklist

                 // System.out.println("ELEMENTO"+xml1+"\n");
                  int grandChildNumber = xml1.countChildren();
                  Enumeration enumGchild = xml1.enumerateChildren();

            for (int j= 0; j<grandChildNumber; j++){  // Estos son los tracks
                   XMLElement xml2 = (XMLElement) enumGchild.nextElement();
                 //  System.out.println("EL NIETO"+xml2);
                   Enumeration tracks = xml2.enumerateChildren();
                   String localiz = null;
                   String title = null;
                   String creator = null;
                   XMLElement xml3 = (XMLElement)tracks.nextElement();
                  while(localiz == null || title ==null || creator ==null){

                        if (xml3.getName().compareTo("location") == 0){
                        localiz = xml3.getContent();
                        System.out.println(localiz);
                        }
                        else if (xml3.getName().compareTo("title") == 0){
                    title = xml3.getContent();
                        System.out.println(title);
                        }
                        else if (xml3.getName().compareTo("creator") == 0){
                        creator = xml3.getContent();
                        System.out.println(creator);
                        }
                        xml3 = (XMLElement)tracks.nextElement();
                  }
                  Cancion cancion = new Cancion(this.numCancion++,title,creator,localiz,this.nombre,this.miIp);
                  playlist.add(cancion);

                }
          }
/* public void realizarConsultaCancion(String[] linea)
 * Param: linea : Es el arreglo de String de la consulta que contiene
 *                la informacion de la cancion y el IP de this.
 * Return: void
 */
public void realizarConsultaCancion(String[] linea){
     this.miIp = linea[1].trim();
         if (linea[2].compareTo("N") == 0){
           // NO CONSIDERO LOS NODOS,
             //ELSE. EL REQUEST VIENE DESDE UN SERVIDOR!!!

         }

         String[] param = linea[0].split(" ");
         List<Cancion> canciones = new ArrayList<Cancion>();
         try {
            this.obtenerBiblioteca(this.playlistActual, biblioteca);
         } catch (Exception ex) {
            Logger.getLogger(nodo.class.getName()).log(Level.SEVERE, null, ex);
         }
         if (param.length < 3){
             System.err.print("Cantidad de parametros invalidas para realizar consulta");
         }
         else if (param.length >= 4){

             String consulta = "";
             for (int i = 2; i<param.length; i++){
              consulta = consulta + param[i] + " ";
             }
              param[2] = consulta.trim();
              System.out.println(param[2]);
         }
        if (param[1].compareToIgnoreCase("-a") == 0){
           canciones = this.buscarCancionPorAutor(param[2]);

        }
        else if (param[1].compareToIgnoreCase("-t") == 0){
           canciones = this.buscarCancionPorTitulo(param[2]);
        }
        else{
            System.err.print("Parametros invalidos para realizar consulta.");

        }
        if (canciones.isEmpty()){

            this.serverOut.println("corte");
        }
        else{
        this.serverOut.println(toStringListaCanciones(canciones)+"corte");

        }
         this.playlistActual.clear();
}

/* public List<String> conectarmeConAlcanzables(List<String> conocidos)
 * Param: conocidos: Lista de todos los inetAddress de los nodos conocidos
 * Return: Lista de String de los nombres de los nodos conocidos
 */
public List<String> conectarmeConAlcanzables(List<String> conocidos){
    List<String> nodos = new ArrayList<String>();
    nodos.add(this.nombre);
    for(int i=0; i<conocidos.size(); i++){
        //CONECTARME!!
        // Debo hacer split del String para poder hacerle getByName.
        //Nota al pasarle mis predecesores al proximo servidor
        //Debo tener cuidado de no pasarle su IP para no entrar en loop
        //Ademas debo pasar como predecesor, mi IP.
    }
    return nodos;
}
/* public List<String> obtenerHostsAlcanzables(List<String> predecesores)
 * Param: predecesores: Lista de los conocidos por mis nodos predecesores
 * Return: Lista de todos los hosts que voy a buscar tal que no haya han sido
 * considerados por mis predecesores.
 */
public List<String> obtenerHostsAlcanzables(List<String> predecesores){
    List<String> hostsAlcanzables = new ArrayList<String>();
    for (int i=0; i<predecesores.size(); i++){
      if (!conocidos.contains(predecesores.get(i))){
          hostsAlcanzables.add(predecesores.get(i));

      }
    }
    return hostsAlcanzables;

}

/* public static String listaToString(List<String> conocidos){
 * Param: lista: Lista de String
 * Retorna: String de todos los elementos de lista
 */
public static String listaToString(List<String> lista){
        String s = "";
    for(int i= 0; i<lista.size(); i++){
         String c = lista.get(i);
          s = s + c + " ";
         }
    return s;

}

//GETTERS

    public String getBiblioteca() {
        return biblioteca;
    }

    public List<String> getConocidos() {
        return conocidos;
    }

    public String getNombre() {
        return nombre;
    }

    public int getPortNumb() {
        return portNumb;
    }

    public ServerSocket getServer() {
        return server;
    }

    public BufferedReader getServerIn() {
        return serverIn;
    }

    public PrintWriter getServerOut() {
        return serverOut;
    }

    public List<Cancion> getPlaylistActual() {
        return playlistActual;
    }


//SETTERS
    public void setBiblioteca(String biblioteca) {
        this.biblioteca = biblioteca;
    }

    public void setConocidos(List<String> conocidos) {
        this.conocidos = conocidos;
    }

    public void setServerIn(BufferedReader in) {
        this.serverIn = in;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public void setServerOut(PrintWriter out) {
        this.serverOut = out;
    }

    public void setPortNumb(int portNumb) {
        this.portNumb = portNumb;
    }

    public void setServer(ServerSocket server) {
        this.server = server;
    }

    public void setPlaylistActual(List<Cancion> playlistActual) {
        this.playlistActual = playlistActual;
    }


}